/**
 * Copyright (c) 2021-2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef ECMASCRIPT_FREE_OBJECT_H
#define ECMASCRIPT_FREE_OBJECT_H

#include "runtime/mem/free_object.h"

#include "plugins/ecmascript/runtime/js_hclass.h"
#include "plugins/ecmascript/runtime/mem/barriers.h"
#include "plugins/ecmascript/runtime/mem/tagged_object-inl.h"

namespace ark::ecmascript {
class FreeObject : public TaggedObject {
public:
    static FreeObject *Cast(uintptr_t object)
    {
        return reinterpret_cast<FreeObject *>(object);
    }
    static FreeObject *FillFreeObject(EcmaVM *vm, uintptr_t address, size_t size);

    inline bool IsEmpty() const
    {
        return Available() == 0;
    }

    inline uintptr_t GetBegin() const
    {
        return reinterpret_cast<uintptr_t>(this);
    }

    inline uintptr_t GetEnd() const
    {
        return reinterpret_cast<uintptr_t>(this) + Available();
    }

    inline void SetAvailable(uint32_t size)
    {
        if (size >= SIZE) {
            SetTaggedSize(JSTaggedValue::PackPrimitiveData(size));
        }
    }

    inline uint32_t Available() const
    {
        auto hclass = GetClass();
        if (hclass != nullptr && (hclass->IsFreeObjectWithOneField() || hclass->IsFreeObjectWithNoneField())) {
            return hclass->GetObjectSize();
        }
        return JSTaggedValue::UnpackPrimitiveData(GetTaggedSize());
    }

    inline bool IsFreeObject() const
    {
        auto hclass = GetClass();
        return (hclass == nullptr) || (hclass->IsFreeObjectWithOneField() || hclass->IsFreeObjectWithTwoField() ||
                                       hclass->IsFreeObjectWithNoneField());
    }

    inline FreeObject *GetNext()
    {
        auto uptr = static_cast<uintptr_t>(JSTaggedValue::UnpackPrimitiveData(GetTaggedNext()));
        return reinterpret_cast<FreeObject *>(uptr);
    }

    inline void SetNext(FreeObject *ptr)
    {
        SetTaggedNext(JSTaggedValue::PackPrimitiveData(ToUintPtr(ptr)));
    }

    /*  // There should be these fields, but that's not friendly to concurrent marker
        ACCESSORS_NATIVE_FIELD(0, Next, FreeObject)
        ACCESSORS_PRIMITIVE_FIELD(1, Size, uint32_t)
    */
    ACCESSORS_START(TaggedObjectSize())
    ACCESSORS_FIXED_SIZE_FIELD(0, TaggedNext, JSTaggedType, JSTaggedType)
    ACCESSORS_FIXED_SIZE_FIELD(1, TaggedSize, JSTaggedType, JSTaggedType)
    ACCESSORS_FINISH(2)
};

static_assert(FreeObject::GetTaggedNextOffset() == ark::mem::FreeObject::GetTaggedNextOffset());
static_assert(FreeObject::GetTaggedSizeOffset() == ark::mem::FreeObject::GetTaggedSizeOffset());

}  // namespace ark::ecmascript
#endif  // ECMASCRIPT_MEM_FREE_OBJECT_H
